home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / ccm / Widgets.py < prev    next >
Encoding:
Python Source  |  2009-03-04  |  55.5 KB  |  1,698 lines

  1. #!/usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3.  
  4. # This program is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU General Public License
  6. # as published by the Free Software Foundation; either version 2
  7. # of the License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful, 
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  17. #
  18. # Authors: Quinn Storm (quinn@beryl-project.org)
  19. #          Patrick Niklaus (marex@opencompositing.org)
  20. #          Guillaume Seguin (guillaume@segu.in)
  21. #          Christopher Williams (christopherw@verizon.net)
  22. # Copyright (C) 2007 Quinn Storm
  23.  
  24. import pygtk
  25. import gtk
  26. import gtk.gdk
  27. import gobject
  28. import cairo, pangocairo
  29. from math import pi, sqrt
  30. import time
  31. import re
  32. import mimetypes
  33. mimetypes.init()
  34.  
  35. from ccm.Utils import *
  36. from ccm.Constants import *
  37. from ccm.Conflicts import *
  38.  
  39. import locale
  40. import gettext
  41. locale.setlocale(locale.LC_ALL, "")
  42. gettext.bindtextdomain("ccsm", DataDir + "/locale")
  43. gettext.textdomain("ccsm")
  44. _ = gettext.gettext
  45.  
  46. #
  47. # Try to use gtk like coding style for consistency
  48. #
  49.  
  50. # Cell Renderer for MultiList
  51.  
  52. class CellRendererColor(gtk.GenericCellRenderer):
  53.     __gproperties__ = {
  54.         'text': (gobject.TYPE_STRING,
  55.                 'color markup text',
  56.                 'The color as markup like this: #rrrrggggbbbbaaaa',
  57.                 '#0000000000000000',
  58.                 gobject.PARAM_READWRITE)
  59.     }
  60.  
  61.     _text  = '#0000000000000000'
  62.     _color = [0, 0, 0, 0]
  63.     _surface = None
  64.     _surface_size = (-1, -1)
  65.  
  66.     def __init__(self):
  67.         gtk.GenericCellRenderer.__init__(self)
  68.  
  69.     def _parse_color(self):
  70.         color = gtk.gdk.color_parse(self._text[:-4])
  71.         alpha = int("0x%s" % self._text[-4:], base=16)
  72.         self._color = [color.red/65535.0, color.green/65535.0, color.blue/65535.0, alpha/65535.0]
  73.  
  74.     def do_set_property(self, property, value):
  75.         if property.name == 'text':
  76.             self._text = value
  77.             self._parse_color()
  78.  
  79.     def do_get_property(self, property):
  80.         if property.name == 'text':
  81.             return self._text
  82.  
  83.     def on_get_size(self, widget, cell_area):
  84.         return (0, 0, 0, 0) # FIXME
  85.  
  86.     def redraw(self, width, height):
  87.         # found in gtk-color-button.c
  88.         CHECK_SIZE  = 4
  89.         CHECK_DARK  = 21845 # 65535 / 3
  90.         CHECK_LIGHT = 43690
  91.  
  92.         width += 10
  93.         height += 10
  94.         self._surface_size = (width, height)
  95.         self._surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
  96.         cr = cairo.Context(self._surface)
  97.  
  98.         x = 0
  99.         y = 0
  100.         colors = [CHECK_DARK, CHECK_LIGHT]
  101.         state = 0
  102.         begin_state = 0
  103.         while y < height:
  104.             while x < width:
  105.                 cr.rectangle(x, y, CHECK_SIZE, CHECK_SIZE)
  106.                 c = colors[state] / 65535.0
  107.                 cr.set_source_rgb(c, c, c)
  108.                 cr.fill()
  109.                 x += CHECK_SIZE
  110.                 state = not state
  111.             state = not begin_state
  112.             begin_state = state
  113.             x = 0
  114.             y += CHECK_SIZE
  115.  
  116.     def on_render(self, window, widget, background_area, cell_area, expose_area, flags):
  117.         cr = window.cairo_create()
  118.  
  119.         height, width = (cell_area.height, cell_area.width)
  120.         sheight, swidth = self._surface_size
  121.         if height > sheight or width > swidth:
  122.             self.redraw(width, height)
  123.  
  124.         cr.rectangle(cell_area.x, cell_area.y, width, height)
  125.         cr.clip()
  126.  
  127.         cr.set_source_surface(self._surface, cell_area.x, cell_area.y)
  128.         cr.paint()
  129.  
  130.         r, g, b, a = self._color
  131.         cr.set_source_rgba(r, g, b, a)
  132.         cr.paint()
  133.  
  134. class PluginView(gtk.TreeView):
  135.     def __init__(self, plugins):
  136.         liststore = gtk.ListStore(str, gtk.gdk.Pixbuf, bool, object)
  137.         self.model = liststore.filter_new()
  138.         gtk.TreeView.__init__(self, self.model)
  139.  
  140.         self.SelectionHandler = None
  141.  
  142.         self.Plugins = set(plugins)
  143.  
  144.         for plugin in sorted(plugins.values(), key=PluginKeyFunc):
  145.             liststore.append([plugin.ShortDesc, Image(plugin.Name, type=ImagePlugin).props.pixbuf, 
  146.                 plugin.Enabled, plugin])
  147.         
  148.         column = self.insert_column_with_attributes(0, _('Plugin'), gtk.CellRendererPixbuf(), pixbuf=1, sensitive=2)
  149.         cell = gtk.CellRendererText()
  150.         cell.props.wrap_width = 200
  151.         column.pack_start(cell)
  152.         column.set_attributes(cell, text=0)
  153.         self.model.set_visible_func(self.VisibleFunc)
  154.         self.get_selection().connect('changed', self.SelectionChanged)
  155.  
  156.     def VisibleFunc(self, model, iter):
  157.         return model[iter][3].Name in self.Plugins
  158.  
  159.     def Filter(self, plugins):
  160.         self.Plugins = set(plugins)
  161.         self.model.refilter()
  162.  
  163.     def SelectionChanged(self, selection):
  164.         model, iter = selection.get_selected()
  165.         if iter is None:
  166.             return self.SelectionHandler(None)
  167.         
  168.         return self.SelectionHandler(model[iter][3])
  169.  
  170. class GroupView(gtk.TreeView):
  171.     def __init__(self, name):
  172.         self.model = gtk.ListStore(str, str)
  173.         gtk.TreeView.__init__(self, self.model)
  174.  
  175.         self.SelectionHandler = None
  176.  
  177.         self.Visible = set()
  178.         
  179.         cell = gtk.CellRendererText()
  180.         cell.props.ypad = 5
  181.         cell.props.wrap_width = 200
  182.         column = gtk.TreeViewColumn(name, cell, text=0)
  183.         self.append_column(column)
  184.  
  185.         self.get_selection().connect('changed', self.SelectionChanged)
  186.         self.hide_all()
  187.         self.props.no_show_all = True
  188.  
  189.     def Update(self, items):
  190.         self.model.clear()
  191.  
  192.         self.model.append([_('All'), 'All'])
  193.  
  194.         length = 0
  195.         for item in items:
  196.             self.model.append([item or _("General"), item])
  197.             if item: # exclude "General" from count
  198.                 length += 1
  199.  
  200.         if length:
  201.             self.show_all()
  202.             self.props.no_show_all = False
  203.         else:
  204.             self.hide_all()
  205.             self.props.no_show_all = True
  206.  
  207.     def SelectionChanged(self, selection):
  208.         model, iter = selection.get_selected()
  209.         if iter is None:
  210.             return None
  211.         
  212.         return self.SelectionHandler(model[iter][1])
  213.  
  214. # Selector Buttons
  215. #
  216. class SelectorButtons(gtk.HBox):
  217.     def __init__(self):
  218.         gtk.HBox.__init__(self)
  219.         self.set_border_width(10)
  220.         self.set_spacing(5)
  221.         self.buttons = []
  222.         self.arrows = []
  223.  
  224.     def clear_buttons(self):
  225.         for widget in (self.arrows + self.buttons):
  226.             widget.destroy()
  227.  
  228.         self.arrows = []
  229.         self.buttons = []
  230.  
  231.     def add_button(self, label, callback):
  232.         arrow = gtk.Arrow(gtk.ARROW_RIGHT, gtk.SHADOW_NONE)
  233.         button = gtk.Button(label)
  234.         button.set_relief(gtk.RELIEF_NONE)
  235.         button.connect('clicked', self.on_button_clicked, callback)
  236.         if self.get_children():
  237.             self.pack_start(arrow, False, False)
  238.             self.arrows.append(arrow)
  239.         self.pack_start(button, False, False)
  240.         self.buttons.append(button)
  241.         self.show_all()
  242.  
  243.     def remove_button(self, pos):
  244.         if pos > len(self.buttons)-1:
  245.             return
  246.         self.buttons[pos].destroy()
  247.         self.buttons.remove(self.buttons[pos])
  248.         if pos > 0:
  249.             self.arrows[pos-1].destroy()
  250.             self.arrows.remove(self.arrows[pos-1])
  251.  
  252.     def on_button_clicked(self, widget, callback):
  253.         callback(selector=True)
  254.  
  255. # Selector Box
  256. #
  257. class SelectorBox(gtk.ScrolledWindow):
  258.     def __init__(self, backgroundColor):
  259.         gtk.ScrolledWindow.__init__(self)
  260.         self.viewport = gtk.Viewport()
  261.         self.viewport.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(backgroundColor))
  262.         self.props.hscrollbar_policy = gtk.POLICY_NEVER
  263.         self.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC
  264.         self.box = gtk.VBox()
  265.         self.box.set_spacing(5)
  266.         self.viewport.add(self.box)
  267.         self.add(self.viewport)
  268.  
  269.     def close(self):
  270.         self.destroy()
  271.         self.viewport.destroy()
  272.         for button in self.box.get_children():
  273.             button.destroy()
  274.         self.box.destroy()
  275.  
  276.     def add_item(self, item, callback, markup="%s", image=None, info=None):
  277.         button = gtk.Button()
  278.         label = Label(wrap=170)
  279.         text = protect_pango_markup(item)
  280.         label.set_markup(markup % text or _("General"))
  281.         labelBox = gtk.VBox()
  282.         labelBox.set_spacing(5)
  283.         labelBox.pack_start(label)
  284.         if info:
  285.             infoLabel = Label()
  286.             infoLabel.set_markup("<span size='small'>%s</span>" % info)
  287.             labelBox.pack_start(infoLabel)
  288.         box = gtk.HBox()
  289.         box.set_spacing(5)
  290.         if image:
  291.             box.pack_start(image, False, False)
  292.         box.pack_start(labelBox)
  293.         button.add(box)
  294.         button.connect("clicked", callback, item)
  295.         button.set_relief(gtk.RELIEF_NONE)
  296.         self.box.pack_start(button, False, False)
  297.  
  298.     def clear_list(self):
  299.         for button in self.box.get_children():
  300.             button.destroy()
  301.     
  302.     def set_item_list(self, list, callback):
  303.         self.clear_list()
  304.         for item in list:
  305.             self.add_item(item)
  306.             
  307.         self.box.show_all()
  308.  
  309. # Scrolled List
  310. #
  311. class ScrolledList(gtk.ScrolledWindow):
  312.     def __init__(self, name):
  313.         gtk.ScrolledWindow.__init__(self)
  314.  
  315.         self.props.hscrollbar_policy = gtk.POLICY_NEVER
  316.         self.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC
  317.  
  318.         self.store = gtk.ListStore(gobject.TYPE_STRING)
  319.     
  320.         self.view = gtk.TreeView(self.store)
  321.         self.view.set_headers_visible(True)
  322.         self.view.insert_column_with_attributes(-1, name, gtk.CellRendererText(), text=0)
  323.         
  324.         self.set_size_request(300, 300)
  325.         
  326.         self.add(self.view)
  327.         
  328.         self.select = self.view.get_selection()
  329.         self.select.set_mode(gtk.SELECTION_SINGLE)
  330.  
  331.     def get_list(self):
  332.         values = []
  333.         iter = self.store.get_iter_first()
  334.         while iter:
  335.             value = self.store.get(iter, 0)[0]
  336.             if value != "":
  337.                 values.append(value)
  338.             iter = self.store.iter_next(iter)    
  339.         return values
  340.  
  341.     def clear(self):
  342.         self.store.clear()
  343.     
  344.     def append(self, value):
  345.         iter = self.store.append()
  346.         self.store.set(iter, 0, value)
  347.  
  348.     def set(self, pos, value):
  349.         iter = self.store.get_iter(pos)
  350.         self.store.set(iter, 0, value)
  351.  
  352.     def delete(self, b):
  353.         selected_rows = self.select.get_selected_rows()[1]
  354.         for path in selected_rows:
  355.             iter = self.store.get_iter(path)
  356.             self.store.remove(iter)
  357.     
  358.     def move_up(self, b):
  359.         selected_rows = self.select.get_selected_rows()[1]
  360.         if len(selected_rows) == 1:
  361.             iter = self.store.get_iter(selected_rows[0])
  362.             prev = self.store.get_iter_first()
  363.             if not self.store.get_path(prev) == self.store.get_path(iter):
  364.                 while prev is not None and not self.store.get_path(self.store.iter_next(prev)) == self.store.get_path(iter):
  365.                     prev = self.store.iter_next(prev)
  366.                 self.store.swap(iter, prev)
  367.  
  368.     def move_down(self, b):
  369.         selected_rows = self.select.get_selected_rows()[1]
  370.         if len(selected_rows) == 1:
  371.             iter = self.store.get_iter(selected_rows[0])
  372.             next = self.store.iter_next(iter)
  373.             if next is not None:
  374.                 self.store.swap(iter, next)
  375.  
  376. # Button modifier selection widget
  377. #
  378. class ModifierSelector (gtk.DrawingArea):
  379.  
  380.     __gsignals__    = {"added" : (gobject.SIGNAL_RUN_FIRST,
  381.                                     gobject.TYPE_NONE, [gobject.TYPE_STRING]),
  382.                        "removed" : (gobject.SIGNAL_RUN_FIRST,
  383.                                     gobject.TYPE_NONE, [gobject.TYPE_STRING])}
  384.  
  385.     _current = []
  386.  
  387.     _base_surface   = None
  388.     _surface        = None
  389.  
  390.     _x0     = 0
  391.     _y0     = 12
  392.     _width  = 100
  393.     _height = 50
  394.  
  395.     _font   = "Sans 12 Bold"
  396.  
  397.     def __init__ (self, mods):
  398.         '''Prepare widget'''
  399.         super (ModifierSelector, self).__init__ ()
  400.         self._current = mods.split ("|")
  401.         modifier = "%s/modifier.png" % PixmapDir
  402.         self._base_surface = cairo.ImageSurface.create_from_png (modifier)
  403.         self.add_events (gtk.gdk.BUTTON_PRESS_MASK)
  404.         self.connect ("expose_event", self.expose)
  405.         self.connect ("button_press_event", self.button_press)
  406.         self.set_size_request (200, 120)
  407.  
  408.         x0, y0, width, height = self._x0, self._y0, self._width, self._height
  409.         self._modifiers = {
  410.             "Shift"     : (x0, y0),
  411.             "Control"   : (x0, y0 + height),
  412.             "Super"     : (x0 + width, y0),
  413.             "Alt"       : (x0 + width, y0 + height)
  414.         }
  415.  
  416.         self._names = {
  417.             "Control"   : "Ctrl"
  418.         }
  419.  
  420.     def set_current (self, value):
  421.         self._current = value.split ("|")
  422.         self.redraw (queue = True)
  423.  
  424.     def get_current (self):
  425.         return "|".join (filter (lambda s: len (s) > 0, self._current))
  426.     current = property (get_current, set_current)
  427.  
  428.     def draw (self, cr, width, height):
  429.         '''The actual drawing function'''
  430.         for mod in self._modifiers:
  431.             x, y = self._modifiers[mod]
  432.             if mod in self._names: text = self._names[mod]
  433.             else: text = mod
  434.             cr.set_source_surface (self._base_surface, x, y)
  435.             cr.rectangle (x, y, self._width, self._height)
  436.             cr.fill_preserve ()
  437.             if mod in self._current:
  438.                 cr.set_source_rgb (0.3, 0.3, 0.3)
  439.                 self.write (cr, x + 23, y + 12, text)
  440.                 cr.set_source_rgb (0.5, 1, 0)
  441.             else:
  442.                 cr.set_source_rgb (0, 0, 0)
  443.             self.write (cr, x + 22, y + 11, text)
  444.  
  445.     def write (self, cr, x, y, text):
  446.         cr.move_to (x, y)
  447.         markup = '''<span font_desc="%s">%s</span>''' % (self._font, text)
  448.         pcr = pangocairo.CairoContext (cr)
  449.         layout = pcr.create_layout ()
  450.         layout.set_markup (markup)
  451.         pcr.show_layout (layout) 
  452.  
  453.     def redraw (self, queue = False):
  454.         '''Redraw internal surface'''
  455.         alloc = self.get_allocation ()
  456.         # Prepare drawing surface
  457.         width, height = alloc.width, alloc.height
  458.         self._surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, width, height)
  459.         cr = cairo.Context (self._surface)
  460.         # Clear
  461.         cr.set_operator (cairo.OPERATOR_CLEAR)
  462.         cr.paint ()
  463.         cr.set_operator (cairo.OPERATOR_OVER)
  464.         # Draw
  465.         self.draw (cr, alloc.width, alloc.height)
  466.         # Queue expose event if required
  467.         if queue:
  468.             self.queue_draw ()
  469.  
  470.     def expose (self, widget, event):
  471.         '''Expose event handler'''
  472.         cr = self.window.cairo_create ()
  473.         if not self._surface:
  474.             self.redraw ()
  475.         cr.set_source_surface (self._surface)
  476.         cr.rectangle (event.area.x, event.area.y,
  477.                       event.area.width, event.area.height)
  478.         cr.clip ()
  479.         cr.paint ()
  480.         return False
  481.  
  482.     def in_rect (self, x, y, x0, y0, x1, y1):
  483.         return x >= x0 and y >= y0 and x <= x1 and y <= y1
  484.     
  485.     def button_press (self, widget, event):
  486.         x, y = event.x, event.y
  487.         mod = ""
  488.  
  489.         for modifier in self._modifiers:
  490.             x0, y0 = self._modifiers[modifier]
  491.             if self.in_rect (x, y, x0, y0,
  492.                              x0 + self._width, y0 + self._height):
  493.                 mod = modifier
  494.                 break
  495.  
  496.         if not len (mod):
  497.             return
  498.         if mod in self._current:
  499.             self._current.remove (mod)
  500.             self.emit ("removed", mod)
  501.         else:
  502.             self._current.append (mod)
  503.             self.emit ("added", mod)
  504.         self.redraw (queue = True)
  505.  
  506. # Edge selection widget
  507. #
  508. class EdgeSelector (gtk.DrawingArea):
  509.  
  510.     __gsignals__    = {"clicked" : (gobject.SIGNAL_RUN_FIRST,
  511.                                     gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,))}
  512.  
  513.     _base_surface   = None
  514.     _surface        = None
  515.     _radius         = 13
  516.     _cradius        = 20
  517.     _coords         = []
  518.  
  519.     def __init__ (self):
  520.         '''Prepare widget'''
  521.         super (EdgeSelector, self).__init__ ()
  522.         background = "%s/display.png" % PixmapDir
  523.         self._base_surface = cairo.ImageSurface.create_from_png (background)
  524.         self.add_events (gtk.gdk.BUTTON_PRESS_MASK)
  525.         self.connect ("expose_event", self.expose)
  526.         self.connect ("button_press_event", self.button_press)
  527.         self.set_size_request (196, 196)
  528.  
  529.         # Useful vars
  530.         x0 = 16
  531.         y0 = 24
  532.         x1 = 181
  533.         y1 = 133
  534.         x2 = x0 + 39
  535.         y2 = y0 + 26
  536.         x3 = x1 - 39
  537.         y3 = y1 - 26
  538.         self._coords = (x0, y0, x1, y1, x2, y2, x3, y3)
  539.  
  540.     def draw (self, cr, width, height):
  541.         '''The actual drawing function'''
  542.         # Useful vars
  543.         x0, y0, x1, y1, x2, y2, x3, y3 = self._coords
  544.         cradius = self._cradius
  545.         radius  = self._radius
  546.  
  547.         cr.set_line_width(1.0)
  548.  
  549.         # Top left edge
  550.         cr.new_path ()
  551.         cr.move_to (x0, y0 + cradius)
  552.         cr.line_to (x0, y0)
  553.         cr.line_to (x0 + cradius, y0)
  554.         cr.arc (x0, y0, cradius, 0, pi / 2)
  555.         cr.close_path ()
  556.         self.set_fill_color (cr, "TopLeft")
  557.         cr.fill_preserve ()
  558.         self.set_stroke_color (cr, "TopLeft")
  559.         cr.stroke ()
  560.         # Top right edge
  561.         cr.new_path ()
  562.         cr.move_to (x1, y0 + cradius)
  563.         cr.line_to (x1, y0)
  564.         cr.line_to (x1 - cradius, y0)
  565.         cr.arc_negative (x1, y0, cradius, pi, pi/2)
  566.         cr.close_path ()
  567.         self.set_fill_color (cr, "TopRight")
  568.         cr.fill_preserve ()
  569.         self.set_stroke_color (cr, "TopRight")
  570.         cr.stroke ()
  571.         # Bottom left edge
  572.         cr.new_path ()
  573.         cr.move_to (x0, y1 - cradius)
  574.         cr.line_to (x0, y1)
  575.         cr.line_to (x0 + cradius, y1)
  576.         cr.arc_negative (x0, y1, cradius, 2 * pi, 3 * pi / 2)
  577.         cr.close_path ()
  578.         self.set_fill_color (cr, "BottomLeft")
  579.         cr.fill_preserve ()
  580.         self.set_stroke_color (cr, "BottomLeft")
  581.         cr.stroke ()
  582.         # Bottom right edge
  583.         cr.new_path ()
  584.         cr.move_to (x1, y1 - cradius)
  585.         cr.line_to (x1, y1)
  586.         cr.line_to (x1 - cradius, y1)
  587.         cr.arc (x1, y1, cradius, pi, 3 * pi / 2)
  588.         cr.close_path ()
  589.         self.set_fill_color (cr, "BottomRight")
  590.         cr.fill_preserve ()
  591.         self.set_stroke_color (cr, "BottomRight")
  592.         cr.stroke ()
  593.         # Top edge
  594.         cr.new_path ()
  595.         cr.move_to (x2 + radius, y0)
  596.         cr.line_to (x3 - radius, y0)
  597.         cr.arc (x3 - radius, y0, radius, 0, pi / 2)
  598.         cr.line_to (x2 + radius, y0 + radius)
  599.         cr.arc (x2 + radius, y0, radius, pi / 2, pi)
  600.         cr.close_path ()
  601.         self.set_fill_color (cr, "Top")
  602.         cr.fill_preserve ()
  603.         self.set_stroke_color (cr, "Top")
  604.         cr.stroke ()
  605.         # Bottom edge
  606.         cr.new_path ()
  607.         cr.move_to (x2 + radius, y1)
  608.         cr.line_to (x3 - radius, y1)
  609.         cr.arc_negative (x3 - radius, y1, radius, 0, - pi / 2)
  610.         cr.line_to (x2 + radius, y1 - radius)
  611.         cr.arc_negative (x2 + radius, y1, radius, - pi / 2, pi)
  612.         cr.close_path ()
  613.         self.set_fill_color (cr, "Bottom")
  614.         cr.fill_preserve ()
  615.         self.set_stroke_color (cr, "Bottom")
  616.         cr.stroke ()
  617.         # Left edge
  618.         cr.new_path ()
  619.         cr.move_to (x0, y2 + radius)
  620.         cr.line_to (x0, y3 - radius)
  621.         cr.arc_negative (x0, y3 - radius, radius, pi / 2, 0)
  622.         cr.line_to (x0 + radius, y2 + radius)
  623.         cr.arc_negative (x0, y2 + radius, radius, 0, 3 * pi / 2)
  624.         cr.close_path ()
  625.         self.set_fill_color (cr, "Left")
  626.         cr.fill_preserve ()
  627.         self.set_stroke_color (cr, "Left")
  628.         cr.stroke ()
  629.         # Right edge
  630.         cr.new_path ()
  631.         cr.move_to (x1, y2 + radius)
  632.         cr.line_to (x1, y3 - radius)
  633.         cr.arc (x1, y3 - radius, radius, pi / 2, pi)
  634.         cr.line_to (x1 - radius, y2 + radius)
  635.         cr.arc (x1, y2 + radius, radius, pi, 3 * pi / 2)
  636.         cr.close_path ()
  637.         self.set_fill_color (cr, "Right")
  638.         cr.fill_preserve ()
  639.         self.set_stroke_color (cr, "Right")
  640.         cr.stroke ()
  641.  
  642.     def set_fill_color (self, cr, edge):
  643.         '''Set painting color for edge'''
  644.         cr.set_source_rgb (0.9, 0.9, 0.9)
  645.  
  646.     def set_stroke_color (self, cr, edge):
  647.         '''Set stroke color for edge'''
  648.         cr.set_source_rgb (0.45, 0.45, 0.45)
  649.  
  650.     def redraw (self, queue = False):
  651.         '''Redraw internal surface'''
  652.         alloc = self.get_allocation ()
  653.         # Prepare drawing surface
  654.         width, height = alloc.width, alloc.height
  655.         self._surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, width, height)
  656.         cr = cairo.Context (self._surface)
  657.         # Draw background
  658.         cr.set_source_surface (self._base_surface)
  659.         cr.paint ()
  660.         # Draw
  661.         self.draw (cr, alloc.width, alloc.height)
  662.         # Queue expose event if required
  663.         if queue:
  664.             self.queue_draw ()
  665.  
  666.     def expose (self, widget, event):
  667.         '''Expose event handler'''
  668.         cr = self.window.cairo_create ()
  669.         if not self._surface:
  670.             self.redraw ()
  671.         cr.set_source_surface (self._surface)
  672.         cr.rectangle (event.area.x, event.area.y,
  673.                       event.area.width, event.area.height)
  674.         cr.clip ()
  675.         cr.paint ()
  676.         return False
  677.  
  678.     def in_circle_quarter (self, x, y, x0, y0, x1, y1, x2, y2, radius):
  679.         '''Args:
  680.             x, y = point coordinates
  681.             x0, y0 = center coordinates
  682.             x1, y1 = circle square top left coordinates
  683.             x2, y2 = circle square bottom right coordinates
  684.             radius = circle radius'''
  685.         if not self.in_rect (x, y, x1, y1, x2, y2):
  686.             return False
  687.         return self.dist (x, y, x0, y0) <= radius
  688.  
  689.     def dist (self, x1, y1, x2, y2):
  690.         return sqrt ((x2 - x1) ** 2 + (y2 - y1) ** 2)
  691.  
  692.     def in_rect (self, x, y, x0, y0, x1, y1):
  693.         return x >= x0 and y >= y0 and x <= x1 and y <= y1
  694.  
  695.     def button_press (self, widget, event):
  696.         x, y = event.x, event.y
  697.         edge = ""
  698.  
  699.         # Useful vars
  700.         x0, y0, x1, y1, x2, y2, x3, y3 = self._coords
  701.         cradius = self._cradius
  702.         radius  = self._radius
  703.  
  704.         if self.in_circle_quarter (x, y, x0, y0, x0, y0,
  705.                                    x0 + cradius, y0 + cradius,
  706.                                    cradius):
  707.             edge = "TopLeft"
  708.         elif self.in_circle_quarter (x, y, x1, y0, x1 - cradius, y0,
  709.                                      x1, y0 + cradius, cradius):
  710.             edge = "TopRight"
  711.         elif self.in_circle_quarter (x, y, x0, y1, x0, y1 - cradius,
  712.                                      x0 + cradius, y1, cradius):
  713.             edge = "BottomLeft"
  714.         elif self.in_circle_quarter (x, y, x1, y1, x1 - cradius, y1 - cradius,
  715.                                      x1, y1, cradius):
  716.             edge = "BottomRight"
  717.         elif self.in_rect (x, y, x2 + radius, y0, x3 - radius, y0 + radius) \
  718.              or self.in_circle_quarter (x, y, x2 + radius, y0, x2, y0,
  719.                                         x2 + radius, y0 + radius, radius) \
  720.              or self.in_circle_quarter (x, y, x3 - radius, y0, x3 - radius, y0,
  721.                                         x3, y0 + radius, radius):
  722.             edge = "Top"
  723.         elif self.in_rect (x, y, x2 + radius, y1 - radius, x3 - radius, y1) \
  724.              or self.in_circle_quarter (x, y, x2 + radius, y1, x2, y1 - radius,
  725.                                         x2 + radius, y1, radius) \
  726.              or self.in_circle_quarter (x, y, x3 - radius, y1,
  727.                                         x3 - radius, y1 - radius,
  728.                                         x3, y1, radius):
  729.             edge = "Bottom"
  730.         elif self.in_rect (x, y, x0, y2 + radius, x0 + radius, y3 - radius) \
  731.              or self.in_circle_quarter (x, y, x0, y2 + radius, x0, y2,
  732.                                         x0 + radius, y2 + radius, radius) \
  733.              or self.in_circle_quarter (x, y, x0, y3 - radius,
  734.                                         x0, y3 - radius,
  735.                                         x0 + radius, y3, radius):
  736.             edge = "Left"
  737.         elif self.in_rect (x, y, x1 - radius, y2 + radius, x1, y3 - radius) \
  738.              or self.in_circle_quarter (x, y, x1, y2 + radius, x1 - radius, y2,
  739.                                         x1, y2 + radius, radius) \
  740.              or self.in_circle_quarter (x, y, x1, y3 - radius,
  741.                                         x1 - radius, y3 - radius,
  742.                                         x1, y3, radius):
  743.             edge = "Right"
  744.  
  745.         if edge:
  746.             self.emit ("clicked", edge, event)
  747.  
  748. # Edge selection widget
  749. #
  750. class SingleEdgeSelector (EdgeSelector):
  751.  
  752.     _current = []
  753.  
  754.     def __init__ (self, edge):
  755.         '''Prepare widget'''
  756.         EdgeSelector.__init__ (self)
  757.         self._current = edge.split ("|")
  758.         self.connect ('clicked', self.edge_clicked)
  759.  
  760.     def set_current (self, value):
  761.         self._current = value.split ("|")
  762.         self.redraw (queue = True)
  763.  
  764.     def get_current (self):
  765.         return "|".join (filter (lambda s: len (s) > 0, self._current))
  766.     current = property (get_current, set_current)
  767.  
  768.     def set_fill_color (self, cr, edge):
  769.         '''Set painting color for edge'''
  770.         if edge in self._current:
  771.             cr.set_source_rgb (0.64, 1.0, 0.09)
  772.         else:
  773.             cr.set_source_rgb (0.80, 0.00, 0.00)
  774.  
  775.     def set_stroke_color (self, cr, edge):
  776.         '''Set stroke color for edge'''
  777.         if edge in self._current:
  778.             cr.set_source_rgb (0.31, 0.60, 0.02)
  779.         else:
  780.             cr.set_source_rgb (0.64, 0.00, 0.00)
  781.  
  782.     def edge_clicked (self, widget, edge, event):
  783.         if not len (edge):
  784.             return
  785.         if edge in self._current:
  786.             self._current.remove (edge)
  787.         else:
  788.             self._current.append (edge)
  789.  
  790.         self.redraw (queue = True)
  791.  
  792. # Global Edge Selector
  793. #
  794. class GlobalEdgeSelector(EdgeSelector):
  795.  
  796.     _settings = []
  797.     _edges = {}
  798.     _text  = {}
  799.     _context = None
  800.  
  801.     def __init__ (self, context, settings=[]):
  802.         EdgeSelector.__init__ (self)
  803.  
  804.         self._context = context
  805.         self._settings = settings
  806.  
  807.         self.connect ("clicked", self.show_popup)
  808.  
  809.         if len (settings) <= 0:
  810.             self.generate_setting_list ()
  811.  
  812.     def set_fill_color (self, cr, edge):
  813.         '''Set painting color for edge'''
  814.         if edge in self._edges:
  815.             cr.set_source_rgb (0.64, 1.0, 0.09)
  816.         else:
  817.             cr.set_source_rgb (0.80, 0.00, 0.00)
  818.  
  819.     def set_stroke_color (self, cr, edge):
  820.         '''Set stroke color for edge'''
  821.         if edge in self._edges:
  822.             cr.set_source_rgb (0.31, 0.60, 0.02)
  823.         else:
  824.             cr.set_source_rgb (0.64, 0.00, 0.00)
  825.  
  826.     def set_settings (self, value):
  827.         self._settings = value
  828.  
  829.     def get_settings (self):
  830.         return self._settings
  831.     settings = property (get_settings, set_settings)
  832.  
  833.     def generate_setting_list (self):
  834.         self._settings = []
  835.  
  836.         def filter_settings(plugin):
  837.             if plugin.Enabled:
  838.                 settings = sorted (GetSettings(plugin), key=SettingKeyFunc)
  839.                 settings = filter (lambda s: s.Type == 'Edge', settings)
  840.                 return settings
  841.             return []
  842.  
  843.         for plugin in self._context.Plugins.values ():
  844.             self._settings += filter_settings (plugin)
  845.  
  846.         for setting in self._settings:
  847.             edges = setting.Value.split ("|")
  848.             for edge in edges:
  849.                 self._edges[edge] = setting
  850.  
  851.     def set_edge_setting (self, setting, edge):
  852.         if not setting:
  853.             if edge in self._edges:
  854.                 self._edges.pop(edge)
  855.             for setting in self._settings:
  856.               value = setting.Value.split ("|")
  857.               if edge in value:
  858.                 value.remove(edge)
  859.                 value = "|".join (filter (lambda s: len (s) > 0, value))
  860.                 setting.Value = value
  861.         else:
  862.             value = setting.Value.split ("|")
  863.             if not edge in value:
  864.                 value.append (edge)
  865.             value = "|".join (filter (lambda s: len (s) > 0, value))
  866.  
  867.             conflict = EdgeConflict (setting, value, settings = self._settings, autoResolve = True)
  868.             if conflict.Resolve (GlobalUpdater):
  869.                 setting.Value = value
  870.                 self._edges[edge] = setting
  871.  
  872.         self._context.Write()
  873.         self.redraw (queue = True)
  874.  
  875.     def show_popup (self, widget, edge, event):
  876.         self._text = {}
  877.         comboBox = gtk.combo_box_new_text ()
  878.  
  879.         comboBox.append_text (_("None"))
  880.         comboBox.set_active (0)
  881.         i = 1
  882.         for setting in self._settings:
  883.             text = "%s: %s" % (setting.Plugin.ShortDesc, setting.ShortDesc)
  884.             comboBox.append_text (text)
  885.             self._text[text] = setting
  886.  
  887.             if edge in setting.Value.split ("|"):
  888.                 comboBox.set_active (i)
  889.             i += 1
  890.  
  891.         comboBox.set_size_request (200, -1)
  892.         comboBox.connect ('changed', self.combo_changed, edge)
  893.  
  894.         popup = Popup (self, child=comboBox, decorated=False, mouse=True, modal=False)
  895.         popup.show_all()
  896.         popup.connect ('focus-out-event', self.focus_out)
  897.  
  898.     def focus_out (self, widget, event):
  899.         combo = widget.get_child ()
  900.         if combo.props.popup_shown:
  901.             return
  902.         gtk_process_events ()
  903.         widget.destroy ()
  904.  
  905.     def combo_changed (self, widget, edge):
  906.         text = widget.get_active_text ()
  907.         setting = None
  908.         if text != _("None"):
  909.             setting = self._text[text]
  910.         self.set_edge_setting (setting, edge)
  911.         popup = widget.get_parent ()
  912.         popup.destroy ()
  913.  
  914. # Popup
  915. #
  916. class Popup (gtk.Window):
  917.  
  918.     def __init__ (self, parent=None, text=None, child=None, decorated=True, mouse=False, modal=True):
  919.         gtk.Window.__init__ (self, gtk.WINDOW_TOPLEVEL)
  920.         self.set_type_hint (gtk.gdk.WINDOW_TYPE_HINT_UTILITY)
  921.         self.set_position (mouse and gtk.WIN_POS_MOUSE or gtk.WIN_POS_CENTER_ALWAYS)
  922.         if parent:
  923.             self.set_transient_for (parent.get_toplevel ())
  924.         self.set_modal (modal)
  925.         self.set_decorated (decorated)
  926.         self.set_property("skip-taskbar-hint", True)
  927.         if text:
  928.             label = gtk.Label (text)
  929.             align = gtk.Alignment ()
  930.             align.set_padding (20, 20, 20, 20)
  931.             align.add (label)
  932.             self.add (align)
  933.         elif child:
  934.             self.add (child)
  935.         gtk_process_events ()
  936.  
  937.     def destroy (self):
  938.         gtk.Window.destroy (self)
  939.         gtk_process_events ()
  940.  
  941. # Key Grabber
  942. #
  943. class KeyGrabber (gtk.Button):
  944.  
  945.     __gsignals__    = {"changed" : (gobject.SIGNAL_RUN_FIRST,
  946.                                     gobject.TYPE_NONE,
  947.                                     [gobject.TYPE_INT, gobject.TYPE_INT]),
  948.                        "current-changed" : (gobject.SIGNAL_RUN_FIRST,
  949.                                     gobject.TYPE_NONE,
  950.                                     [gobject.TYPE_INT, gobject.TYPE_INT])}
  951.  
  952.     key     = 0
  953.     mods    = 0
  954.     handler = None
  955.     popup   = None
  956.  
  957.     label   = None
  958.  
  959.     def __init__ (self, key = 0, mods = 0, label = None):
  960.         '''Prepare widget'''
  961.         super (KeyGrabber, self).__init__ ()
  962.  
  963.         self.key = key
  964.         self.mods = mods
  965.  
  966.         self.label = label
  967.  
  968.         self.connect ("clicked", self.begin_key_grab)
  969.         self.set_label ()
  970.  
  971.     def begin_key_grab (self, widget):
  972.         self.add_events (gtk.gdk.KEY_PRESS_MASK)
  973.         self.popup = Popup (self, _("Please press the new key combination"))
  974.         self.popup.show_all()
  975.         self.handler = self.popup.connect ("key-press-event",
  976.                                            self.on_key_press_event)
  977.         while gtk.gdk.keyboard_grab (self.popup.window) != gtk.gdk.GRAB_SUCCESS:
  978.             time.sleep (0.1)
  979.  
  980.     def end_key_grab (self):
  981.         gtk.gdk.keyboard_ungrab (gtk.get_current_event_time ())
  982.         self.popup.disconnect (self.handler)
  983.         self.popup.destroy ()
  984.  
  985.     def on_key_press_event (self, widget, event):
  986.         mods = event.state & gtk.accelerator_get_default_mod_mask ()
  987.  
  988.         if event.keyval in (gtk.keysyms.Escape, gtk.keysyms.Return) \
  989.             and not mods:
  990.             if event.keyval == gtk.keysyms.Escape:
  991.                 self.emit ("changed", self.key, self.mods)
  992.             self.end_key_grab ()
  993.             self.set_label ()
  994.             return
  995.  
  996.         key = gtk.gdk.keyval_to_lower (event.keyval)
  997.         if (key == gtk.keysyms.ISO_Left_Tab):
  998.             key = gtk.keysyms.Tab
  999.  
  1000.         if gtk.accelerator_valid (key, mods) \
  1001.            or (key == gtk.keysyms.Tab and mods):
  1002.             self.set_label (key, mods)
  1003.             self.end_key_grab ()
  1004.             self.key = key
  1005.             self.mods = mods
  1006.             self.emit ("changed", self.key, self.mods)
  1007.             return
  1008.  
  1009.         self.set_label (key, mods)
  1010.  
  1011.     def set_label (self, key = None, mods = None):
  1012.         if self.label:
  1013.             if key != None and mods != None:
  1014.                 self.emit ("current-changed", key, mods)
  1015.             gtk.Button.set_label (self, self.label)
  1016.             return
  1017.         if key == None and mods == None:
  1018.             key = self.key
  1019.             mods = self.mods
  1020.         label = gtk.accelerator_name (key, mods)
  1021.         if not len (label):
  1022.             label = _("Disabled")
  1023.         gtk.Button.set_label (self, label)
  1024.  
  1025. # Match Button
  1026. #
  1027. class MatchButton(gtk.Button):
  1028.  
  1029.     __gsignals__    = {"changed" : (gobject.SIGNAL_RUN_FIRST,
  1030.                                     gobject.TYPE_NONE,
  1031.                                     [gobject.TYPE_STRING])}
  1032.  
  1033.     prefix = {\
  1034.             _("Window Title"): 'title',
  1035.             _("Window Role"): 'role',
  1036.             _("Window Name"): 'name',
  1037.             _("Window Class"): 'class',
  1038.             _("Window Type"): 'type',
  1039.             _("Window ID"): 'xid',
  1040.     }
  1041.  
  1042.     symbols = {\
  1043.             _("And"): '&',
  1044.             _("Or"): '|'
  1045.     }
  1046.  
  1047.     match   = None
  1048.  
  1049.     def __init__ (self, entry = None):
  1050.         '''Prepare widget'''
  1051.         super (MatchButton, self).__init__ ()
  1052.  
  1053.         self.entry = entry
  1054.         self.match = entry.get_text()
  1055.  
  1056.         self.add (Image (name = gtk.STOCK_ADD, type = ImageStock,
  1057.                          size = gtk.ICON_SIZE_BUTTON))
  1058.         self.connect ("clicked", self.run_edit_dialog)
  1059.  
  1060.     def set_match (self, value):
  1061.         self.match = value
  1062.         self.entry.set_text(value)
  1063.         self.entry.activate()
  1064.  
  1065.     def get_xprop (self, regexp, proc = "xprop"):
  1066.         proc = os.popen (proc)
  1067.         output = proc.readlines ()
  1068.         rex = re.compile (regexp)
  1069.         value = ""
  1070.         for line in output:
  1071.             if rex.search (line):
  1072.                 m = rex.match (line)
  1073.                 value = m.groups () [-1]
  1074.                 break
  1075.  
  1076.         return value
  1077.  
  1078.     # Regular Expressions taken from beryl-settings
  1079.     def grab_value (self, widget, value_widget, type_widget):
  1080.         value = ""
  1081.         prefix = self.prefix[type_widget.get_active_text()]
  1082.  
  1083.         if prefix == "type":
  1084.             value = self.get_xprop("^_NET_WM_WINDOW_TYPE\(ATOM\) = _NET_WM_WINDOW_TYPE_(\w+)")
  1085.             value = value.lower().capitalize()
  1086.         elif prefix == "role":
  1087.             value = self.get_xprop("^WM_WINDOW_ROLE\(STRING\) = \"([^\"]+)\"")
  1088.         elif prefix == "name":
  1089.             value = self.get_xprop("^WM_CLASS\(STRING\) = \"([^\"]+)\"")
  1090.         elif prefix == "class":
  1091.             value = self.get_xprop("^WM_CLASS\(STRING\) = \"([^\"]+)\", \"([^\"]+)\"")
  1092.         elif prefix == "title":
  1093.             value = self.get_xprop("^_NET_WM_NAME\(UTF8_STRING\) = ([^\n]+)")
  1094.             if value:
  1095.                 list = value.split(", ")
  1096.                 value = ""
  1097.                 for hex in list:
  1098.                     value += "%c" % int(hex, 16)
  1099.             else:
  1100.                 value = self.get_xprop("^WM_NAME\(STRING\) = \"([^\"]+)\"")
  1101.         elif prefix == "id":
  1102.             value = self.get_xprop("^xwininfo: Window id: ([^\s]+)", "xwininfo")
  1103.  
  1104.         value_widget.set_text(value)
  1105.  
  1106.     def generate_match (self, type, value, relation, invert):
  1107.         match = ""
  1108.         text = self.match
  1109.  
  1110.         prefix = self.prefix[type]
  1111.         symbol = self.symbols[relation]
  1112.  
  1113.         # check if the current match needs some brackets
  1114.         if len(text) > 0 and text[-1] != ')' and text[0] != '(':
  1115.             match = "(%s)" % text
  1116.         else:
  1117.             match = text
  1118.  
  1119.         if invert:
  1120.             match = "%s %s !(%s=%s)" % (match, symbol, prefix, value)
  1121.         elif len(match) > 0:
  1122.             match = "%s %s %s=%s" % (match, symbol, prefix, value)
  1123.         else:
  1124.             match = "%s=%s" % (prefix, value)
  1125.  
  1126.         self.set_match (match)
  1127.  
  1128.     def _check_entry_value (self, entry, dialog):
  1129.         is_valid = False
  1130.         value = entry.get_text()
  1131.         if value != "":
  1132.             is_valid = True
  1133.         dialog.set_response_sensitive(gtk.RESPONSE_OK, is_valid)
  1134.  
  1135.     def run_edit_dialog (self, widget):
  1136.         '''Run dialog to generate a match'''
  1137.  
  1138.         self.match = self.entry.get_text ()
  1139.  
  1140.         dlg = gtk.Dialog (_("Edit match"))
  1141.         dlg.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
  1142.         dlg.set_transient_for (self.get_parent ().get_toplevel ())
  1143.         dlg.add_button (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
  1144.         dlg.add_button (gtk.STOCK_ADD, gtk.RESPONSE_OK).grab_default ()
  1145.         dlg.set_response_sensitive(gtk.RESPONSE_OK, False)
  1146.         dlg.set_default_response (gtk.RESPONSE_OK)
  1147.  
  1148.         table = gtk.Table ()
  1149.  
  1150.         rows = []
  1151.  
  1152.         # Type
  1153.         label = Label (_("Type"))
  1154.         type_chooser = gtk.combo_box_new_text ()
  1155.         for type in self.prefix.keys ():
  1156.             type_chooser.append_text (type)
  1157.         type_chooser.set_active (0)
  1158.         rows.append ((label, type_chooser))
  1159.  
  1160.         # Value
  1161.         label = Label (_("Value"))
  1162.         box = gtk.HBox ()
  1163.         box.set_spacing (5)
  1164.         entry = gtk.Entry ()
  1165.         entry.connect ('changed', self._check_entry_value, dlg)
  1166.         button = gtk.Button (_("Grab"))
  1167.         button.connect ('clicked', self.grab_value, entry, type_chooser)
  1168.         box.pack_start (entry, True, True)
  1169.         box.pack_start (button, False, False)
  1170.         rows.append ((label, box))
  1171.  
  1172.         # Relation
  1173.         label = Label (_("Relation"))
  1174.         relation_chooser = gtk.combo_box_new_text ()
  1175.         for relation in self.symbols.keys ():
  1176.             relation_chooser.append_text (relation)
  1177.         relation_chooser.set_active (0)
  1178.         rows.append ((label, relation_chooser))
  1179.  
  1180.         # Invert
  1181.         label = Label (_("Invert"))
  1182.         check = gtk.CheckButton ()
  1183.         rows.append ((label, check))
  1184.  
  1185.         row = 0
  1186.         for label, widget in rows:
  1187.             table.attach(label, 0, 1, row, row+1, yoptions=0, xpadding=TableX, ypadding=TableY)
  1188.             table.attach(widget, 1, 2, row, row+1, yoptions=0, xpadding=TableX, ypadding=TableY)
  1189.             row += 1
  1190.  
  1191.         dlg.vbox.pack_start (table)
  1192.         dlg.vbox.set_spacing (5)
  1193.         dlg.show_all ()
  1194.  
  1195.         response = dlg.run ()
  1196.         dlg.destroy ()
  1197.         if response == gtk.RESPONSE_OK:
  1198.             type     = type_chooser.get_active_text ()
  1199.             value    = entry.get_text ()
  1200.             relation = relation_chooser.get_active_text ()
  1201.             invert   = check.get_active ()
  1202.             self.generate_match (type, value, relation, invert)
  1203.  
  1204. class FileButton (gtk.Button):
  1205.     __gsignals__    = {"changed" : (gobject.SIGNAL_RUN_FIRST,
  1206.                                     gobject.TYPE_NONE,
  1207.                                     [gobject.TYPE_STRING])}
  1208.     _directory = False
  1209.     _context   = None
  1210.     _image     = False
  1211.     _path      = ""
  1212.  
  1213.     def __init__ (self, context, entry, directory=False, image=False, path=""):
  1214.         gtk.Button.__init__ (self)
  1215.  
  1216.         self._entry = entry
  1217.         self._directory = directory
  1218.         self._context = context
  1219.         self._image = image
  1220.         self._path = path
  1221.  
  1222.         self.set_tooltip_text(_("Browse..."))
  1223.         self.set_image(gtk.image_new_from_stock(
  1224.             gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON))
  1225.         self.connect('clicked', self.open_dialog)
  1226.  
  1227.     def set_path (self, value):
  1228.         self._path = value
  1229.         self._entry.set_text (value)
  1230.         self._entry.activate ()
  1231.  
  1232.     def create_filter(self):
  1233.         filter = gtk.FileFilter ()
  1234.         if self._image:
  1235.             filter.set_name (_("Images"))
  1236.             filter.add_pattern ("*.png")
  1237.             filter.add_pattern ("*.jpg")
  1238.             filter.add_pattern ("*.jpeg")
  1239.             filter.add_pattern ("*.svg")
  1240.         else:
  1241.             filter.add_pattern ("*")
  1242.             filter.set_name (_("File"))
  1243.  
  1244.         return filter
  1245.  
  1246.     def check_type (self, filename):
  1247.         if filename.find (".") == -1:
  1248.             return True
  1249.         ext = filename.split (".") [-1]
  1250.  
  1251.         try:
  1252.             mime = mimetypes.types_map [".%s" %ext]
  1253.         except KeyError:
  1254.             return True
  1255.  
  1256.         if self._image:
  1257.             require = FeatureRequirement (self._context, 'imagemime:' + mime)
  1258.             return require.Resolve ()
  1259.  
  1260.         return True
  1261.  
  1262.     def update_preview (self, widget):
  1263.         path = widget.get_preview_filename ()
  1264.         if path is None or os.path.isdir (path):
  1265.             widget.get_preview_widget ().set_from_file (None)
  1266.             return
  1267.         try:
  1268.             pixbuf = gtk.gdk.pixbuf_new_from_file_at_size (path, 128, 128)
  1269.         except gobject.GError:
  1270.             return
  1271.         widget.get_preview_widget ().set_from_pixbuf (pixbuf)
  1272.  
  1273.     def open_dialog (self, widget):
  1274.         b = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)
  1275.         if self._directory:
  1276.             title = _("Open directory...")
  1277.         else:
  1278.             title = _("Open file...")
  1279.  
  1280.         chooser = gtk.FileChooserDialog (title = title, buttons = b)
  1281.         if self._directory:
  1282.             chooser.set_action (gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
  1283.         else:
  1284.             chooser.set_filter (self.create_filter ())
  1285.  
  1286.         if self._path and os.path.exists (self._path):
  1287.             chooser.set_filename (self._path)
  1288.         else:
  1289.             chooser.set_current_folder (os.environ.get("HOME"))
  1290.  
  1291.         if self._image:
  1292.             chooser.set_use_preview_label (False)
  1293.             chooser.set_preview_widget (gtk.Image ())
  1294.             chooser.connect ("selection-changed", self.update_preview)
  1295.  
  1296.         ret = chooser.run ()
  1297.  
  1298.         filename = chooser.get_filename ()
  1299.         chooser.destroy ()
  1300.         if ret == gtk.RESPONSE_OK:
  1301.             if self._directory or self.check_type (filename):
  1302.                 self.set_path (filename)
  1303.  
  1304. # About Dialog
  1305. #
  1306. class AboutDialog (gtk.AboutDialog):
  1307.     def __init__ (self, parent):
  1308.         gtk.AboutDialog.__init__ (self)
  1309.         self.set_transient_for (parent)
  1310.  
  1311.         self.set_name (_("CompizConfig Settings Manager"))
  1312.         self.set_version (Version)
  1313.         self.set_comments (_("This is a settings manager for the CompizConfig configuration system."))
  1314.         self.set_copyright ("Copyright \xC2\xA9 2007-2008 Patrick Niklaus/Christopher Williams/Guillaume Seguin/Quinn Storm")
  1315.         self.set_translator_credits (_("translator-credits"))
  1316.         self.set_authors (["Patrick Niklaus <marex@opencompositing.org>",
  1317.                            "Christopher Williams <christopherw@verizon.net>",
  1318.                            "Guillaume Seguin <guillaume@segu.in>",
  1319.                            "Quinn Storm <quinn@beryl-project.org>"])
  1320.         self.set_artists (["Andrew Wedderburn <andrew.wedderburn@gmail.com>",
  1321.                            "Patrick Niklaus <marex@opencompositing.org>",
  1322.                            "Gnome Icon Theme Team"])
  1323.         if IconTheme.lookup_icon("ccsm", 64, 0):
  1324.             icon = IconTheme.load_icon("ccsm", 64, 0)
  1325.             self.set_logo (icon)
  1326.         self.set_website ("http://www.compiz-fusion.org")
  1327.  
  1328. # Error dialog
  1329. #
  1330. class ErrorDialog (gtk.MessageDialog):
  1331.     '''Display an error dialog'''
  1332.  
  1333.     def __init__ (self, parent, message):
  1334.         gtk.MessageDialog.__init__ (self, parent,
  1335.                                     gtk.DIALOG_DESTROY_WITH_PARENT,
  1336.                                     gtk.MESSAGE_ERROR,
  1337.                                     gtk.BUTTONS_CLOSE)
  1338.         self.set_position (gtk.WIN_POS_CENTER)
  1339.         self.set_markup (message)
  1340.         self.set_title (_("An error has occured"))
  1341.         self.set_transient_for (parent)
  1342.         self.set_modal (True)
  1343.         self.show_all ()
  1344.         self.connect ("response", lambda *args: self.destroy ())
  1345.  
  1346. # Warning dialog
  1347. #
  1348. class WarningDialog (gtk.MessageDialog):
  1349.     '''Display a warning dialog'''
  1350.  
  1351.     def __init__ (self, parent, message):
  1352.         gtk.MessageDialog.__init__ (self, parent,
  1353.                                     gtk.DIALOG_DESTROY_WITH_PARENT,
  1354.                                     gtk.MESSAGE_WARNING,
  1355.                                     gtk.BUTTONS_YES_NO)
  1356.         self.set_position (gtk.WIN_POS_CENTER)
  1357.         self.set_markup (message)
  1358.         self.set_title (_("Warning"))
  1359.         self.set_transient_for (parent)
  1360.         self.connect_after ("response", lambda *args: self.destroy ())
  1361.  
  1362. # Plugin Button
  1363. #
  1364. class PluginButton (gtk.HBox):
  1365.  
  1366.     __gsignals__    = {"clicked"   : (gobject.SIGNAL_RUN_FIRST,
  1367.                                       gobject.TYPE_NONE,
  1368.                                       []),
  1369.                        "activated" : (gobject.SIGNAL_RUN_FIRST,
  1370.                                       gobject.TYPE_NONE,
  1371.                                       [])}
  1372.  
  1373.     _plugin = None
  1374.  
  1375.     def __init__ (self, plugin, useMissingImage = False):
  1376.         gtk.HBox.__init__(self)
  1377.         self._plugin = plugin
  1378.  
  1379.         image = Image (plugin.Name, ImagePlugin, 32, useMissingImage)
  1380.         label = Label (plugin.ShortDesc, 120)
  1381.         label.connect ('style-set', self.style_set)
  1382.         box = gtk.HBox ()
  1383.         box.set_spacing (5)
  1384.         box.pack_start (image, False, False)
  1385.         box.pack_start (label)
  1386.  
  1387.         button = PrettyButton ()
  1388.         button.connect ('clicked', self.show_plugin_page)
  1389.         button.set_tooltip_text (plugin.LongDesc)
  1390.         button.add (box)
  1391.  
  1392.         if plugin.Name != 'core':
  1393.             enable = gtk.CheckButton ()
  1394.             enable.set_tooltip_text(_("Enable %s") % plugin.ShortDesc)
  1395.             enable.set_active (plugin.Enabled)
  1396.             enable.set_sensitive (plugin.Context.AutoSort)
  1397.             self._toggled_handler = enable.connect ("toggled", self.enable_plugin)
  1398.             PluginSetting (plugin, enable, self._toggled_handler)
  1399.             self.pack_start (enable, False, False)
  1400.         self.pack_start (button, False, False)
  1401.  
  1402.         self.set_size_request (220, -1)
  1403.  
  1404.     StyleBlock = 0
  1405.  
  1406.     def style_set (self, widget, previous):
  1407.         if self.StyleBlock > 0:
  1408.             return
  1409.         self.StyleBlock += 1
  1410.         widget.modify_fg(gtk.STATE_NORMAL, widget.style.text[gtk.STATE_NORMAL])
  1411.         self.StyleBlock -= 1
  1412.  
  1413.     def enable_plugin (self, widget):
  1414.  
  1415.         plugin = self._plugin
  1416.         conflicts = plugin.Enabled and plugin.DisableConflicts or plugin.EnableConflicts
  1417.  
  1418.         conflict = PluginConflict (plugin, conflicts)
  1419.  
  1420.         if conflict.Resolve ():
  1421.             plugin.Enabled = widget.get_active ()
  1422.         else:
  1423.             widget.handler_block(self._toggled_handler)
  1424.             widget.set_active (plugin.Enabled)
  1425.             widget.handler_unblock(self._toggled_handler)
  1426.  
  1427.         plugin.Context.Write ()
  1428.         GlobalUpdater.UpdatePlugins()
  1429.         plugin.Context.UpdateExtensiblePlugins ()
  1430.         self.emit ('activated')
  1431.  
  1432.     def show_plugin_page (self, widget):
  1433.         self.emit ('clicked')
  1434.  
  1435.     def filter (self, text, level=FilterAll):
  1436.         found = False
  1437.         if level & FilterName:
  1438.             if (text in self._plugin.Name.lower ()
  1439.             or text in self._plugin.ShortDesc.lower ()):
  1440.                 found = True
  1441.         if not found and level & FilterLongDesc:
  1442.             if text in self._plugin.LongDesc.lower():
  1443.                 found = True
  1444.         if not found and level & FilterCategory:
  1445.             if text == None \
  1446.             or (text == "" and self._plugin.Category.lower() == "") \
  1447.             or (text != "" and text in self._plugin.Category.lower()):
  1448.                 found = True
  1449.  
  1450.         return found
  1451.  
  1452.     def get_plugin (self):
  1453.         return self._plugin
  1454.  
  1455. # Category Box
  1456. #
  1457. class CategoryBox(gtk.VBox):
  1458.  
  1459.     _plugins = None
  1460.     _unfiltered_plugins = None
  1461.     _buttons = None
  1462.     _context = None
  1463.     _name    = ""
  1464.     _tabel   = None
  1465.     _alignment = None
  1466.     _current_cols = 0
  1467.     _current_plugins = 0
  1468.  
  1469.     def __init__ (self, context, name, plugins=None, categoryIndex=0):
  1470.         gtk.VBox.__init__ (self)
  1471.  
  1472.         self.set_spacing (5)
  1473.  
  1474.         self._context = context
  1475.         if plugins is not None:
  1476.             self._plugins = plugins
  1477.         else:
  1478.             self._plugins = []
  1479.  
  1480.         if not plugins:
  1481.             for plugin in context.Plugins.values ():
  1482.                 if plugin.Category == name:
  1483.                     self._plugins.append (plugin)
  1484.  
  1485.         self._plugins.sort(key=PluginKeyFunc)
  1486.         self._name = name
  1487.         text = name or 'Uncategorized'
  1488.  
  1489.         # Keep unfiltered list of plugins for correct background icon loading
  1490.         self._unfiltered_plugins = self._plugins
  1491.  
  1492.         header = gtk.HBox ()
  1493.         header.set_border_width (5)
  1494.         header.set_spacing (10)
  1495.         label = Label ('', -1)
  1496.         label.set_markup ("<span color='#aaa' size='x-large' weight='800'>%s</span>" % _(text))
  1497.  
  1498.         icon = text.lower ().replace (" ", "_")
  1499.         image = Image (icon, ImageCategory)
  1500.         header.pack_start (image, False, False)
  1501.         header.pack_start (label, False, False)
  1502.  
  1503.         self._table = gtk.Table ()
  1504.         self._table.set_border_width (10)
  1505.  
  1506.         # load icons now only for the first 3 categories
  1507.         dontLoadIcons = (categoryIndex >= 3);
  1508.  
  1509.         self._buttons = []
  1510.         for plugin in self._plugins:
  1511.             button = PluginButton(plugin, dontLoadIcons)
  1512.             self._buttons.append(button)
  1513.  
  1514.         self._alignment = gtk.Alignment (0, 0, 1, 1)
  1515.         self._alignment.set_padding (0, 20, 0, 0)
  1516.         self._alignment.add (gtk.HSeparator ())
  1517.  
  1518.         self.pack_start (header, False, False)
  1519.         self.pack_start (self._table, False, False)
  1520.         self.pack_start (self._alignment)
  1521.  
  1522.     def show_separator (self, show):
  1523.         children = self.get_children ()
  1524.         if show:
  1525.             if self._alignment not in children:
  1526.                 self.pack_start (self._alignment)
  1527.         else:
  1528.             if self._alignment in children:
  1529.                 self.remove(self._alignment)
  1530.  
  1531.     def filter_buttons (self, text, level=FilterAll):
  1532.         self._plugins = []
  1533.         for button in self._buttons:
  1534.             if button.filter (text, level=level):
  1535.                 self._plugins.append (button.get_plugin())
  1536.  
  1537.         return bool(self._plugins)
  1538.  
  1539.     def rebuild_table (self, ncols, force = False):
  1540.         if (not force and ncols == self._current_cols
  1541.         and len (self._plugins) == self._current_plugins):
  1542.             return
  1543.         self._current_cols = ncols
  1544.         self._current_plugins = len (self._plugins)
  1545.  
  1546.         children = self._table.get_children ()
  1547.         if children:
  1548.             for child in children:
  1549.                 self._table.remove(child)
  1550.  
  1551.         row = 0
  1552.         col = 0
  1553.         for button in self._buttons:
  1554.             if button.get_plugin () in self._plugins:
  1555.                 self._table.attach (button, col, col+1, row, row+1, 0, xpadding=TableX, ypadding=TableY)
  1556.                 col += 1
  1557.                 if col == ncols:
  1558.                     col = 0
  1559.                     row += 1
  1560.         self.show_all ()
  1561.  
  1562.     def get_buttons (self):
  1563.         return self._buttons
  1564.  
  1565.     def get_plugins (self):
  1566.         return self._plugins
  1567.  
  1568.     def get_unfiltered_plugins (self):
  1569.         return self._unfiltered_plugins
  1570.  
  1571. # Plugin Window
  1572. #
  1573. class PluginWindow(gtk.ScrolledWindow):
  1574.     __gsignals__    = {"show-plugin" : (gobject.SIGNAL_RUN_FIRST,
  1575.                                         gobject.TYPE_NONE,
  1576.                                         [gobject.TYPE_PYOBJECT])}
  1577.  
  1578.     _not_found_box = None
  1579.     _style_block   = 0
  1580.     _context       = None
  1581.     _categories    = None
  1582.     _viewport      = None
  1583.     _boxes         = None
  1584.     _box           = None
  1585.  
  1586.     def __init__ (self, context, categories=[], plugins=[]):
  1587.         gtk.ScrolledWindow.__init__ (self)
  1588.  
  1589.         self._categories = {}
  1590.         self._boxes = []
  1591.         self._context = context
  1592.         pool = plugins or self._context.Plugins.values()
  1593.         if len (categories):
  1594.             for plugin in pool:
  1595.                 category = plugin.Category
  1596.                 if category in categories:
  1597.                     if not category in self._categories:
  1598.                         self._categories[category] = []
  1599.                     self._categories[category].append(plugin)
  1600.         else:
  1601.             for plugin in pool:
  1602.                 category = plugin.Category
  1603.                 if not category in self._categories:
  1604.                     self._categories[category] = []
  1605.                 self._categories[category].append(plugin)
  1606.  
  1607.         self.props.hscrollbar_policy = gtk.POLICY_NEVER
  1608.         self.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC
  1609.         self.connect ('size-allocate', self.rebuild_boxes)
  1610.  
  1611.         self._box = gtk.VBox ()
  1612.         self._box.set_spacing (5)
  1613.  
  1614.         self._not_found_box = NotFoundBox ()
  1615.  
  1616.         categories = sorted(self._categories, key=CategoryKeyFunc)
  1617.         for (i, category) in enumerate(categories):
  1618.             plugins = self._categories[category]
  1619.             category_box = CategoryBox(context, category, plugins, i)
  1620.             self.connect_buttons (category_box)
  1621.             self._boxes.append (category_box)
  1622.             self._box.pack_start (category_box, False, False)
  1623.  
  1624.         viewport = gtk.Viewport ()
  1625.         viewport.connect("style-set", self.set_viewport_style)
  1626.         viewport.set_focus_vadjustment (self.get_vadjustment ())
  1627.         viewport.add (self._box)
  1628.         self.add (viewport)
  1629.  
  1630.     def connect_buttons (self, category_box):
  1631.         buttons = category_box.get_buttons ()
  1632.         for button in buttons:
  1633.             button.connect('clicked', self.show_plugin_page)
  1634.  
  1635.     def set_viewport_style (self, widget, previous):
  1636.         if self._style_block > 0:
  1637.             return
  1638.         self._style_block += 1
  1639.         widget.modify_bg(gtk.STATE_NORMAL, widget.style.base[gtk.STATE_NORMAL])
  1640.         self._style_block -= 1
  1641.  
  1642.     def filter_boxes (self, text, level=FilterAll):
  1643.         found = False
  1644.  
  1645.         for box in self._boxes:
  1646.             found |= box.filter_buttons (text, level)
  1647.  
  1648.         viewport = self.get_child ()
  1649.         child    = viewport.get_child ()
  1650.  
  1651.         if not found:
  1652.             if child is not self._not_found_box:
  1653.                 viewport.remove (self._box)
  1654.                 viewport.add (self._not_found_box)
  1655.             self._not_found_box.update (text)
  1656.         else:
  1657.             if child is self._not_found_box:
  1658.                 viewport.remove (self._not_found_box)
  1659.                 viewport.add (self._box)
  1660.  
  1661.         self.queue_resize()
  1662.         self.show_all()
  1663.  
  1664.     def rebuild_boxes (self, widget, request):
  1665.         ncols = request.width / 220
  1666.         width = ncols * (220 + 2 * TableX) + 40
  1667.         if width > request.width:
  1668.             ncols -= 1
  1669.  
  1670.         pos = 0
  1671.         last_box = None
  1672.         children = self._box.get_children ()
  1673.         for box in self._boxes:
  1674.             plugins = box.get_plugins ()
  1675.             if len (plugins) == 0:
  1676.                 if box in children:
  1677.                     self._box.remove(box)
  1678.             else:
  1679.                 if last_box:
  1680.                     last_box.show_separator (True)
  1681.  
  1682.                 if box not in children:
  1683.                     self._box.pack_start (box, False, False)
  1684.                     self._box.reorder_child (box, pos)
  1685.                 box.rebuild_table (ncols)
  1686.                 box.show_separator (False)
  1687.                 pos += 1
  1688.  
  1689.                 last_box = box
  1690.  
  1691.     def get_categories (self):
  1692.         return self._categories.keys ()
  1693.  
  1694.     def show_plugin_page (self, widget):
  1695.         plugin = widget.get_plugin ()
  1696.         self.emit ('show-plugin', plugin)
  1697.  
  1698.